/*** 
 * @Author: Yanan Chen
 * @Date: 2021-11-02 13:47:03
 * @LastEditTime: 2021-11-30 14:31:14
 * @LastEditors: Yanan Chen
 * @Description: 
 * @FilePath: \fw\esp32\Src\APP\app_storage.h
 * @
 */
#ifndef PRJ_APP_STORAGE_H
#define PRJ_APP_STORAGE_H
#include <stdio.h>
#include <stdlib.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/timers.h"
#include "hal_function.h"
#include "nvs.h"
#include "nvs_handle.hpp"




//partition mapping
enum class PartitionDataType{
    tu8,
    tu16,
    tu32,
    ts8,
    ts16,
    ts32,
    tstring,
};


//partition mapping
enum class cfgPartitionData{
    ssid = 0,
    passwd,
    runcnt,
    max,
};



typedef struct
{
    const char dataType;
    const char *key;
}nvsDataMap;

typedef struct
{
    const char *partitionname;
    const char *key;
    const PartitionDataType data_type;
}nvsPartitionMap;

extern nvsPartitionMap cfgMapData[];
extern nvsPartitionMap userMapData[];
class AppStorage
{
public:
    AppStorage(){};
    ~AppStorage(){};
    static void init()
    {
        esp_err_t err = nvs_flash_init();
        if (err == ESP_ERR_NVS_NO_FREE_PAGES || err == ESP_ERR_NVS_NEW_VERSION_FOUND) {
            // NVS partition was truncated and needs to be erased
            // Retry nvs_flash_init
            ESP_ERROR_CHECK(nvs_flash_erase());
            err = nvs_flash_init();
        }
        ESP_ERROR_CHECK( err );
    }

    template <typename p,typename t>
    static int get(p type,t &val,int size)
    {
        nvsPartitionMap *mapaddr = AppStorage::getMapaddr(type);
        if(mapaddr)
        {
            return AppStorage::read(mapaddr[static_cast<int>(type)].partitionname,mapaddr[static_cast<int>(type)].key,val,size);
        }
        return 0;
    }

    template <typename p,typename t>
    static int get(p type,t &val)
    {
        nvsPartitionMap *mapaddr = AppStorage::getMapaddr(type);
        if(mapaddr)
        {
            return AppStorage::read(mapaddr[static_cast<int>(type)].partitionname,mapaddr[static_cast<int>(type)].key,val); 
        }
        return 0;
    }

    template <typename p,typename t>
    static int set(p type,t &val,int size)
    {
        nvsPartitionMap *mapaddr = AppStorage::getMapaddr(type);
        if(mapaddr)
        {
            return AppStorage::write(mapaddr[static_cast<int>(type)].partitionname,mapaddr[static_cast<int>(type)].key,val,size);
        }
        return 0;
    }

    template <typename p,typename t>
    static int set(p type,t &val)
    {
        nvsPartitionMap *mapaddr = AppStorage::getMapaddr(type);
        if(mapaddr)
        {
            return AppStorage::write(mapaddr[static_cast<int>(type)].partitionname,mapaddr[static_cast<int>(type)].key,val); 
        }
        return 0;
    }


private:
    template <typename p>
    static nvsPartitionMap *getMapaddr(p &pname)
    {
        nvsPartitionMap *mapaddr = 0;

        if(std::is_same<p,cfgPartitionData>::value)
        {
            mapaddr = cfgMapData;
        }
        return mapaddr;
    }

    template <typename t>
    static int write(const char *partition,const char *key,t data,int size)
    {
        void *pdata = (void *)data;
        nvs_handle_t my_handle;
        esp_err_t err;

        // Open
        err = nvs_open(partition, NVS_READWRITE, &my_handle);
        if (err != ESP_OK) return err;
        err = nvs_set_blob(my_handle, key, pdata, size);
        if (err != ESP_OK) return err;
        // Commit
        err = nvs_commit(my_handle);
        if (err != ESP_OK) return err;
        // Close
        nvs_close(my_handle);
        return ESP_OK;
    }
    template <typename t>
    static int read(const char *partition,const char *key,t &data,int size)
    {
        void *podata = (void *)data;
        nvs_handle_t my_handle;
        esp_err_t err;
        void *pdata;
        // Open
        err = nvs_open(partition, NVS_READWRITE, &my_handle);
        if (err != ESP_OK) return 0;
        // Read
        size_t required_size = 0;  // value will default to 0, if not set yet in NVS
        // obtain required memory space to store blob being read from NVS
        err = nvs_get_blob(my_handle, key, NULL, &required_size);
        if (err != ESP_OK && err != ESP_ERR_NVS_NOT_FOUND) return 0;
        if (required_size == 0) {
             nvs_close(my_handle);
            return 0;
        } else {
            pdata = malloc(required_size);
            if(pdata)
            {
                err = nvs_get_blob(my_handle, key, pdata, &required_size);
                if (err != ESP_OK) {
                    free(pdata);
                    return 0;
                }
            }
            else
            {
                ESP_LOGE("AppStorage", "mem_err(Free memory: %d bytes)", esp_get_free_heap_size());
                return 0;
            }
        }
        if(size > required_size)
        {
            size = required_size;
        }
        if(size)
        {
            memcpy(podata, pdata, size);
        }
        if(pdata) free(pdata);
        // Close
        nvs_close(my_handle);
        return size;
    }

    template <typename t>
    static int write(const char *partition,const char *key,t value)
    {
        esp_err_t err;
        std::shared_ptr<nvs::NVSHandle> handle = nvs::open_nvs_handle(partition, NVS_READWRITE, &err);
        
        if (err != ESP_OK) 
        {
            ESP_LOGE("AppStorage","Error (%s) opening NVS handle!\n", esp_err_to_name(err));
        } 
        else 
        {
            err = handle->set_item(key, value);
            // Commit written value.
            // After setting any values, nvs_commit() must be called to ensure changes are written
            // to flash storage. Implementations may write to storage at other times,
            // but this is not guaranteed.
            err = handle->commit();
            if (err != ESP_OK) 
            {
                ESP_LOGE("AppStorage","Error (%s) WRITE NVS handle!\n", esp_err_to_name(err));
            }  
        }
        return err;
    }
    template <typename t>
    static int read(const char *partition,const char *key,t &value)
    {
        esp_err_t err;
        std::shared_ptr<nvs::NVSHandle> handle = nvs::open_nvs_handle(partition, NVS_READWRITE, &err);
        
        if (err != ESP_OK) 
        {
            ESP_LOGE("AppStorage","Error (%s) opening NVS handle!\n", esp_err_to_name(err));
        } 
        else 
        {
            err = handle->get_item(key, value);
            // Commit written value.
            // After setting any values, nvs_commit() must be called to ensure changes are written
            // to flash storage. Implementations may write to storage at other times,
            // but this is not guaranteed.
            if (err != ESP_OK) 
            {
                ESP_LOGE("AppStorage","Error (%s) WRITE NVS handle!\n", esp_err_to_name(err));
            } 
        }
        return err;
    }


    //template <typename t>
    //static int nvsRead(nvsPartition p,t dataKey,);
    //nvsWrite(nvsPartition p,);
    



};


#endif